﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright(C) < 1998 - 2024 > Glodon Company Limited
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

using Glodon.Lookup.ViewModels.Objects;
using Glodon.Lookup.Views.Utils;

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Glodon.Lookup.Views.Controls
{
    public class SnoopPath : ItemsControl
    {
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            var source = ItemsSource as ObservableCollection<SnoopAction>;
            source!.CollectionChanged += Source_CollectionChanged;
        }

        // 数据源变化时滚动 SnoopPath 内容到最右端, 以避免最新历史节点不可见
        private void Source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
            {
                VisualUtils.FindVisualChild<ScrollViewer>(this).ScrollToRightEnd();
            }
        }

        /// <summary>
        /// 获取目标控件的宽度范围是否已经初始化
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        public static bool GetIsWidthInitialized(DependencyObject target)
        {
            return (bool)target.GetValue(IsWidthInitializedProperty);
        }

        /// <summary>
        /// 设置目标控件的宽度范围是否已经初始化
        /// </summary>
        /// <param name="target"></param>
        /// <param name="value"></param>
        public static void SetIsWidthInitialized(DependencyObject target, bool value)
        {
            target.SetValue(IsWidthInitializedProperty, value);
        }

        // Using a DependencyProperty as the backing store for IsWidthInitialized.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsWidthInitializedProperty =
            DependencyProperty.Register("IsWidthInitialized", typeof(bool), typeof(SnoopPath), new PropertyMetadata(false));


        public double MinItemWidth
        {
            get { return (double)GetValue(MinItemWidthProperty); }
            set { SetValue(MinItemWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinItemWidthProperty =
            DependencyProperty.Register("MinItemWidth", typeof(double), typeof(SnoopPath), new PropertyMetadata(20.0));

        /// <summary>
        /// 设置当SnoopPath剩余空间改变时，是否重新调整每个元素尺寸：
        /// 当剩余空间变少时缩小所有可缩小元素，以尽可能显示全部元素；
        /// 当剩余空间变多时拉伸所有可拉伸元素，以尽可能占满剩余空间。
        ///   可缩小：实际尺寸大于最小尺寸；
        ///   可拉伸：实际尺寸小于最大尺寸
        /// </summary>
        public bool CanResizeItems
        {
            get { return (bool)GetValue(CanResizeItemsProperty); }
            set { SetValue(CanResizeItemsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CanResizeItemsProperty =
            DependencyProperty.Register("CanResizeItems", typeof(bool), typeof(SnoopPath), new UIPropertyMetadata(OnCanResizeItemsChanged));

        /// <summary>
        /// 当 CanResizeItems 设为 true 时，SnoopPath尺寸改变时会自动调整元素尺寸
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static void OnCanResizeItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                if (d is SnoopPath snoopPath)
                {
                    snoopPath.SizeChanged += (s, e) =>
                    {
                        snoopPath.ResizeItems();
                        VisualUtils.FindVisualChild<ScrollViewer>(snoopPath).ScrollToRightEnd();
                    };
                }
            }
        }
        /// <summary>
        /// 根据容器内的TextBlock的文字渲染宽度设定容器的最大最小宽度
        /// </summary>
        /// <param name="textPanel"></param>
        private void SetItemTextPanelWidthRange(Panel itemPanel)
        {
            if (itemPanel != null)
            {
                //首次设置Border宽度时，TextBlock 的实际宽度等于文字渲染宽度
                var textBorder = VisualUtils.FindVisualChild<Border>(itemPanel);
                textBorder.MinWidth = Math.Min(MinItemWidth, textBorder.ActualWidth);
                textBorder.MaxWidth = textBorder.ActualWidth;
                SetIsWidthInitialized(textBorder, true);
                var textBlock = VisualUtils.FindVisualChild<TextBlock>(textBorder);
                //文字改变时根据新的文字渲染宽度重新设置 Border 宽度范围
                //(因为设置Border宽度后，TextBlock的实际宽度跟随Border，不再跟随其文字)
                textBlock.TargetUpdated += (sender, args) =>
                {
                    if (args.Property.Name.Equals(nameof(TextBlock.Text)))
                    {
                        double dpiScale = VisualTreeHelper.GetDpi(this).PixelsPerDip;
                        FormattedText formattedText = new FormattedText(
                            textBlock.Text,
                            System.Globalization.CultureInfo.CurrentCulture,
                            FlowDirection.LeftToRight,
                            new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch),
                            textBlock.FontSize,
                            textBlock.Foreground, dpiScale);

                        textBorder.MinWidth = Math.Min(MinItemWidth, formattedText.Width + textBlock.Padding.Left + textBlock.Padding.Right);
                        textBorder.MaxWidth = formattedText.Width + textBlock.Padding.Left + textBlock.Padding.Right;
                    }
                };
            }
        }

        /// <summary>
        /// 每当有新元素加载完成时，SnoopPath 剩余空间发生改变，需要重新调整元素尺寸
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnItemPanelLoaded(object sender, RoutedEventArgs e)
        {
            var panel = sender as Panel;
            if (panel != null && CanResizeItems)
            {
                SetItemTextPanelWidthRange(panel);
                ResizeItems();
            }
        }



        /// <summary>
        /// 尝试缩每个Item中Border宽度，以使当前 SnoopPath 内容宽度不超过 SnooPath 控件宽度
        /// </summary>
        private void ResizeItems()
        {
            Panel itemsPanel = null;
            var firstItemContainer = ItemContainerGenerator.ContainerFromIndex(0);
            if (firstItemContainer != null)
            {
                itemsPanel = VisualTreeHelper.GetParent(firstItemContainer) as Panel;
            }
            if (itemsPanel == null) return;
            var allItemsContent = VisualUtils.FindAllVisualChildren<ContentPresenter>(itemsPanel, false);
            double allChildrenWidth = 0;
            foreach (var contentPresenter in allItemsContent)
            {
                allChildrenWidth += contentPresenter.ActualWidth;
            }
            double widthNeedResizeTotal = ActualWidth - allChildrenWidth;
            var itemsCanResize = new List<FrameworkElement>();
            double widthCanResizeTotal = 0;
            //遍历Items, 找到所有位于Item中并且可以调整宽度的Border
            foreach (var item in Items)
            {
                if (ItemContainerGenerator.ContainerFromItem(item) is FrameworkElement container)
                {
                    var textBorder = VisualUtils.FindVisualChild<Border>(container);
                    if (textBorder != null && GetIsWidthInitialized(textBorder))
                    {

                        bool canSqueeze = textBorder.MinWidth < textBorder.ActualWidth && widthNeedResizeTotal < 0;
                        bool canExpand = textBorder.MaxWidth > textBorder.ActualWidth && widthNeedResizeTotal > 0;
                        if (canSqueeze || canExpand)
                        {
                            itemsCanResize.Add(textBorder);
                            if (canSqueeze)
                            {
                                widthCanResizeTotal += textBorder.ActualWidth - textBorder.MinWidth;
                            }
                            else
                            {
                                widthCanResizeTotal += textBorder.MaxWidth - textBorder.ActualWidth;
                            }
                        }
                    }
                }
            }
            //对可以调整宽度的Border，逐个调整宽度
            foreach (var itemControl in itemsCanResize)
            {
                double widthCanResize;
                if (widthNeedResizeTotal < 0)
                {
                    widthCanResize = itemControl.MinWidth - itemControl.ActualWidth;
                }
                else
                {
                    widthCanResize = itemControl.MaxWidth - itemControl.ActualWidth;
                }
                double widthCanResizeTotalAbs = Math.Abs(widthCanResizeTotal);
                double widthNeedResizeTotalAbs = Math.Abs(widthNeedResizeTotal);
                //当总所需调整值大于总可调整值时，当前元素需要调整的值==当前元素可调整的值
                //否则，当前元素需要调整的值 == 当前元素的可调整值 * 总需要调整的值/总可调整值
                double widthNeedResize = widthCanResize * Math.Abs(widthNeedResizeTotal)
                                        / Math.Max(widthNeedResizeTotalAbs, widthCanResizeTotalAbs);
                itemControl.Width = itemControl.ActualWidth + widthNeedResize;
            }
        }
    }
}
